
var roomMgr = require("./roommgr");
var userMgr = require("./usermgr");
var mjutils = require('./mjutils');
var db = require("../utils/db");
var crypto = require("../utils/crypto");

var games = {};
var gamesIdBase = 0;

var ACTION_CHUPAI = 1;
var ACTION_MOPAI = 2;
var ACTION_PENG = 3;
var ACTION_GANG = 4;
var ACTION_HU = 5;
var ACTION_ZIMO = 6;
var ACTION_CHI = 7;

var gameSeatsOfUsers = {};
var gmFnDict = {
    uid: '', // 指定修改userid的牌
    type: '',
    fn: '', // 1为开局指定配牌，2为游戏中下一张牌配牌，3为
    // fn为1，指定配牌种类（0，1，2 对应花色的清一色牌）9为小鸡牌；
    // fn为2，指定下次发牌种类（0，1，2，3为牌类型）
    nums: '', // NO.对应指令
}

var getMJType = mjutils.getMJType;
const getGangPlayDict = function (paistr) {
    let result;
    if (!paistr) {
        return;
    }
    switch (paistr) {
        case "30,31,32,33":
        case "27,28,29":
        case "0,9,18":
        case "8,17,26":
        // case "1,10,19":
        // case "7,16,25": // 二八杠
            result = {
                paiarr: paistr.split(",").map((e) => e * 1),
                gangtype: "teshugang",
            };
            break;
        default:
            result = {
                paiarr: paistr.split(",").map((e) => e * 1),
                gangtype: "teshugang",
                isadd: true, // 加杠标识
            };
            break;
    }
    return result;
};

var dissolvingList = [];
// 洗牌
function shuffle(game, mjType) {
    var mahjongs = game.mahjongs;
    let dict = {
        // 筒
        '0': [0, 8],
        // 条
        '1': [9, 17],
        // 万
        '2': [18, 26],
        // 四风、中发白
        '3': [27, 33],
        // 特殊杠
        // 中发白
        '4': [27, 28, 29],
        // 东南西北
        '5': [30, 31, 32, 33],
        // 幺牌
        '6': [0, 9, 18],
        // 九牌
        '7': [8, 17, 26]
    }

    //筒 (0 ~ 8 表示筒子
    var count = 0;
    for (var i = 0; i < 9; ++i) {
        for (var c = 0; c < 4; ++c) {
            mahjongs[count] = i;
            count++;
        }
    }

    //条 9 ~ 17表示条子
    for (var i = 9; i < 18; ++i) {
        for (var c = 0; c < 4; ++c) {
            mahjongs[count] = i;
            count++;
        }
    }

    //万 18 ~ 26表示万
    for (var i = 18; i < 27; ++i) {
        for (var c = 0; c < 4; ++c) {
            mahjongs[count] = i;
            count++;
        }
    }

    // 中27 发28 白29 东30 南31 西32 北33 
    for (var i = 27; i < 34; i++) {
        for (var c = 0; c < 4; ++c) {
            mahjongs[count] = i;
            count++;
        }
    }

    // for (var i = 0; i < count; ++i) {
    for (var i = 0; i < mahjongs.length; ++i) {
        var lastIndex = mahjongs.length - 1 - i;
        var index = Math.floor(Math.random() * lastIndex);
        var t = mahjongs[index];
        mahjongs[index] = mahjongs[lastIndex];
        mahjongs[lastIndex] = t;
    }
    console.log('mahjongs and length', mahjongs, mahjongs.length);
    // gm修改，二次洗牌，首次分牌
    if (mjType && gmFnDict.fn === '1') {
        let { uid, nums } = gmFnDict;
        // let seatInx = game.gameSeats.findIndex(e => e.userId == uid);
        let seatInx = 0, seat;
        let changeLen = { now: 0, len: 12 };
        if (Number(nums) >= 4) {
            changeLen.len = 8;
        }
        let findNextPaiWithRange = function (nowInx, paiRange, nums) {
            // let result = 0;
            let nextInx = -1;
            if (changeLen.now > changeLen.len) {
                return;
            }
            if (Number(nums) < 4) {
                for (let jnx = nowInx + 1; jnx < mahjongs.length - nowInx; jnx++) {
                    const em = mahjongs[jnx];
                    if (em >= paiRange[0] && em < paiRange[1]) {
                        nextInx = jnx;
                        changeLen.now++;
                        break;
                    }
                }
            } else {
                for (let jnx = nowInx + 1; jnx < mahjongs.length - nowInx; jnx++) {
                    const em = mahjongs[jnx];
                    if (paiRange.includes(em)) {
                        nextInx = jnx;
                        changeLen.now++;
                        break;
                    }
                }
            }
            // let sl = mahjongs.splice(nextInx, 1);
            if (nextInx > 0) {
                let t = 0;
                t = mahjongs[nowInx];
                mahjongs[nowInx] = mahjongs[nextInx];
                mahjongs[nextInx] = t;
            }
            // return result;
        }
        for (let i = 0; i < game.gameSeats.length; i++) {
            const e = game.gameSeats[i];
            if (e.userId == uid) {
                seatInx = i;
                seat = e;
                break;
            }
        }
        let paiRange = dict[nums];
        if (!paiRange) {
            return;
        }
        // 遍历已有牌your
        // let sa = []
        for (let inx = 0; inx < mahjongs.length; inx++) {
            // const el = mahjongs[inx];
            if (inx == seatInx || inx % (seatInx + 4) == 0) {
                // sa.push(el);
                findNextPaiWithRange(inx, paiRange, nums);
            }
        }
        console.log('调整过后', mahjongs);
    }
    // console.log('mahjongs and length', mahjongs, mahjongs.length);
}

function dice(game) {
    var dices = game.dices;
    dices.push((Math.floor(Math.random() * 100) % 6) + 1);
    dices.push((Math.floor(Math.random() * 1000) % 6) + 1);
}

function maiMa(game) {
    if (game.currentIndex == game.mahjongs.length) {
        return -1;
    }

    return game.mahjongs[game.mahjongs.length - 1];
}

function getMaScore(ma) {
    var score = 0;
    if (ma < 27) {
        score = (ma % 9) + 1;
    } else {
        score = 10;
    }

    return score;
}

function mopai(game, seatIndex, mjType) {
    // 已摸完牌
    if (game.currentIndex == game.mahjongs.length) {
        return -1;
    }

    var seat = game.gameSeats[seatIndex];
    var mahjongs = seat.holds;
    var pai;
    // console.log('mahjongs', mahjongs, game.currentIndex, seatIndex);
    // console.log('mopai countmap', seat.countMap);
    // 拿指定类型的牌
    // if (mjType) {
    //     pai = mopai_mjtype(game, mjType);
    //     // var type = getMJType(gameSeatData.holds[0]);
    // } else {
    // }
    pai = game.mahjongs[game.currentIndex];

    mahjongs.push(pai);

    //统计牌的数目 ，用于快速判定（空间换时间）
    var c = seat.countMap[pai];
    if (c == null) {
        c = 0;
    }

    seat.countMap[pai] = c + 1;
    game.currentIndex++;

    return pai;
}

function deal(game) {
    // 强制清0
    game.currentIndex = 0;

    //每人13张 一共 13*4 ＝ 52张 庄家多一张 53张
    var seatIndex = game.button;
    for (var i = 0; i < 52; ++i) {
        var mahjongs = game.gameSeats[seatIndex].holds;
        if (mahjongs == null) {
            mahjongs = [];
            game.gameSeats[seatIndex].holds = mahjongs;
        }
        mopai(game, seatIndex);
        seatIndex++;
        seatIndex %= 4;
    }

    console.log('current inx', game.currentIndex);
    // 会牌挂起
    // game.huipai = game.mahjongs[game.currentIndex] >= 33 ? 0 : game.mahjongs[game.currentIndex] + 1; // 前端加1，则服务端减1，统计的时候记得加1
    let next_pai = game.mahjongs[game.currentIndex];

    game.huipai = next_pai;
    game.realhuipai = mjutils.getRealHuipai(next_pai);
    console.log('----------------当局会牌--------', game.realhuipai);
    
    game.currentIndex++;
    // userMgr.broacastInRoom('set_huipai', { huipai: game.huipai, game: game }, game.gameSeats[game.button].userId, true);

    // 庄家多摸最后一张
    const lastpai = mopai(game, game.button);
    // 当前轮设置为庄家
    game.turn = game.button;
    return lastpai;
}

// 检查是否可以碰
function checkCanPeng(game, seatData, targetPai) {
    if (seatData.hasMingPai) {
        return;
    }
    // 手牌不能少于5张
    if (seatData.holds.length < 5) {
        return;
    }
    // if (getMJType(targetPai) == seatData.que) {
    //     return;
    // }

    // 会牌不能peng
    if (targetPai == game.realhuipai) {
        return;
    }
    var count = seatData.countMap[targetPai];
    if (count != null && count >= 2) {
        seatData.canPeng = true;
    }
}

// 检查是否可以吃
function checkCanChi(game, seatData, targetPai) {
    // 手牌不能少于5张
    if (seatData.holds.length < 5) {
        return;
    }
    // 会牌不能chi
    if (targetPai == game.realhuipai) {
        return;
    }

    // var count = seatData.countMap[targetPai];
    // if (count != null && count >= 2) {
    //     seatData.canPeng = true;
    // }
    const { holds } = seatData;
    // const isShun = mjutils.isShunKe([...holds, targetPai], 0, 'shun')
    let isShun = false;
    const shun_arr = mjutils.shunArrFn(Array.from(new Set([...holds, +targetPai])), +targetPai);
    if (shun_arr.length > 0) {
        isShun = true;
    }
    console.log(shun_arr,'----------判断是不是能吃---------', isShun);
    if (isShun) {
        seatData.canChi = true;
        // 几种能吃牌的组合
        // seatData.chiPai = mjutils.shunArrFn([...holds, targetPai], targetPai);
        seatData.chiPai = shun_arr;
        console.log('----吃牌组合----------', seatData.chiPai);
    }
}

// 检查是否可以点杠
function checkCanDianGang(game, seatData, targetPai) {
    //如果没有牌了，则不能再杠
    if (game.mahjongs.length <= game.currentIndex) {
        return;
    }
    // 手牌不能少于5张
    if (seatData.holds.length < 5) {
        return;
    }

    // 会牌不能杠
    if (targetPai == game.realhuipai) {
        return;
    }
    // if (getMJType(targetPai) == seatData.que) {
    //     return;
    // }
    var count = seatData.countMap[targetPai];
    if (count != null && count >= 3) {
        // if (!seatData.hasMingPai || seatData.kou.indexOf(targetPai) != -1) {
        // }
        seatData.canGang = true;
        seatData.gangPai.push(targetPai);
        return;
    }
}

// 检查是否可以暗杠
function checkCanAnGang(game, seatData) {
    //如果没有牌了，则不能再杠
    if (game.mahjongs.length <= game.currentIndex) {
        return;
    }
    // 手牌不能少于5张
    if (seatData.holds.length < 5) {
        return;
    }

    for (var key in seatData.countMap) {
        var pai = parseInt(key);
        var c = seatData.countMap[key];
        if (c != null && c == 4 && pai != game.realhuipai) {
            //  || seatData.kou.indexOf(pai) != -1
            // if (!seatData.hasMingPai) {
            seatData.canGang = true;
            seatData.gangPai.push(pai);
            // }
        }
    }
}

// 检查是否可以弯杠(自己摸起来的时候)
function checkCanWanGang(game, seatData) {
    //如果没有牌了，则不能再杠
    if (game.mahjongs.length <= game.currentIndex) {
        return;
    }
    // 手牌不能少于5张
    if (seatData.holds.length < 5) {
        return;
    }
    // 从碰过的牌中选
    if (seatData.pengs.length > 0) {
        for (var i = 0; i < seatData.pengs.length; ++i) {
            var pai_peng = seatData.pengs[i];
            if (seatData.countMap[pai_peng] == 1 && pai_peng != game.realhuipai) {
                seatData.canGang = true;
                seatData.gangPai.push(pai_peng);
            }
        }
    }
}

// 检查特殊杠
function checkSpecialGang(game, seatData) {
    //如果没有牌了，则不能再杠
    let { mahjongs, currentIndex } = game;
    if (mahjongs.length <= currentIndex) {
        return;
    }
    // 手牌不能少于5张
    if (seatData.holds.length < 5) {
        return;
    }
    let { userId, countMap, teshugangs } = seatData;
    
    // 检查是否有人要胡，要碰 要杠 吃
    var hasActions = false;
    // 验证其他玩家
    const checkGameSeats = function () {
        for (var i = 0; i < game.gameSeats.length; ++i) {
            // 玩家自己不检查
            if (game.turn == i) {
                continue;
            }
            var ddd = game.gameSeats[i];
            // 已经和牌的不再检查
            if (ddd.hued) { continue; }

            // checkCanHu(game, ddd, pai);
            if (seatData.lastFangGangSeat == -1) {
                if (ddd.canHu && ddd.guoHuFan >= 0 && ddd.tingMap[pai].fan <= ddd.guoHuFan) {
                    // console.log("ddd.guoHuFan:" + ddd.guoHuFan);
                    ddd.canHu = false;
                    userMgr.sendMsg(ddd.userId, 'guohu_push');
                }
            }
            checkCanPeng(game, ddd, pai);
            // // 吃牌只对上家
            // const isShangJia = seatIndex == 3 ? i == 0 : seatIndex == i - 1;
            // console.log('---------------判断上家----------------', seatIndex, i, isShangJia);
            // if (isShangJia) {
            //     checkCanChi(game, ddd, pai);
            // }
            // checkCanDianGang(game, ddd, pai);
            // // mjutils.matchShun(ddd, pai);
            // // 特殊杠只能自己摸
            // // checkSpecialGang(game, ddd);

            if (hasOperations(ddd)) {
                sendOperations(game, ddd, game.chuPai);
                hasActions = true;
            }
        }
    }
    // 从碰过的牌中选
    const checkHoldsAndPai = function (seatData, countMap) {
        let gangPaiArr = [];
        const teshugangdict = {
            fenggangs: {
                '30': 1,
                '31': 1,
                '32': 1,
                '33': 1,
            }, // 风杠
            zfbgangs: {
                '27': 1,
                '28': 1,
                '29': 1
            }, // 中发白杠
            yaogangs: {
                '0': 1,
                '9': 1,
                '18': 1
            }, // 幺杠
            jiugangs: {
                '8': 1,
                '17': 1,
                '26': 1
            }, // 九杠
            // ergangs: {
            //     '1': 1,
            //     '10': 1,
            //     '19': 1
            // }, // 二杠
            // bagangs: {
            //     '7': 1,
            //     '16': 1,
            //     '25': 1
            // }, // 八杠
        }
        let keyArr = Object.keys(teshugangdict);
        for (let inx = 0; inx < keyArr.length; inx++) {
            const e = keyArr[inx];
            let item = teshugangdict[e];

            // 检查符合要求的
            let resarr = [];
            let teshuarr = Object.keys(item);
            resarr = teshuarr.filter(k => countMap[k] >= item[k]);
            if (resarr.length === teshuarr.length) {
                gangPaiArr.push(teshuarr.join(','));
            }
        }
        
        if (gangPaiArr.length > 0) {
            seatData.canGang = true;
            seatData.gangPai = [...seatData.gangPai, ...gangPaiArr];
        }
        return;
    }
    // 加杠验证
    const checkJiaGangPai = function (seatData, countMap, jgangs) {
        // let jiaGang = seatData.teshugangs.filter((e) => checks.includes(e));
        console.log('jiagangs', seatData.teshugangs, jgangs);
        // let checks = ["27,28,29", "0,9,18", "8,17,26"];

        // TODO 加杠
        // 特殊杠可以加杠
        // 每次取可杠数的第一个
        const add_gang = jgangs[0] * 1;
        // for (let ix = 0; ix < seatData.teshugangs.length; ix++) {
        //     const el = seatData.teshugangs[ix];
        // }
        const isGangs = seatData.teshugangs.filter(gang => {
            const num_gang = gang.split(',').map(e => +e);
            return num_gang.indexOf(add_gang) > -1;
        });
        if (isGangs && isGangs.length > 0 && countMap[add_gang] > 0) {
            // 加杠前先判断其他家能否碰
            // 自己摸被其他玩家检查？
            // checkGameSeats()

            // console.log(add_gang, 'can jia gang');
            console.log(!hasActions, '其他玩家没有动作');
            // 其他玩家没有碰或者胡，则可以加杠
            if (!hasActions) {
                // setTimeout(() => {
                seatData.canGang = true;
                let gang_new = isGangs[0].split(',').map(e => +e);
                gang_new.push(add_gang);
                gang_new.sort((a, b) => Number(a) - Number(b));
                // gang_new.split(',').map(e => +e).sort();
                console.log(gang_new, 'gang new');
                seatData.gangPai.push(gang_new.join(","));
                // }, 500);
            }
        }

    }

    // TODO 对比已有的特殊杠，如果是3牌杠，则是加杠而不进入重新杠
    // let teshuNum = [0,8,9,17,18,26,27,28,29]; // 3杠的数字
    let teshuFlag = false;
    let jgangs = [];
    if (teshugangs && teshugangs.length > 0) {
        const jiaGanngDict = {
            '27': 1,
            '28': 1,
            '29': 1, // 中发白杠
            '0': 1,
            '9': 1,
            '18': 1, // 幺杠
            '8': 1,
            '17': 1,
            '26': 1, // 九杠
            '30': 1,
            '31': 1,
            '32': 1,
            '33': 1, //风杠
            // // 二八杠
            // '1': 1,
            // '10': 1,
            // '19': 1,
            // '7': 1,
            // '16': 1,
            // '25': 1,
        }
        const teshuFull = teshugangs.join(',').split(',');
        jgangs = Object.keys(jiaGanngDict).filter(e => countMap[e] >= jiaGanngDict[e] && teshuFull.indexOf(e) > -1);
        // console.log(jgangs, 'jgangs and teshu gang', teshugangs);
        // 手上总共的牌满足杠，可以加杠
        if (jgangs && jgangs.length > 0) {
            teshuFlag = true;
        }
    }
    
    if (!teshuFlag) {
        checkHoldsAndPai(seatData, countMap);
    } else {
        // 加杠验证,手牌和摸牌都验
        checkJiaGangPai(seatData, countMap, jgangs);
    }
    // console.log(seatData.gangpai, 'gang pai');
}
// do 特殊杠, pai 过来是字符串形式
function doSpecialGang(game, seatData, pai) {
    var seatIndex = seatData.seatIndex;
    var turnSeat = game.gameSeats[game.turn];
    var gameTurn = turnSeat.seatIndex;
    console.log('special', seatData.seatIndex, pai);
    // console.log('special', seatData, turnSeat, pai);
    let { countMap, holds, teshugangs } = seatData;
    // var numOfCnt = seatData.countMap[pai]; // countMap需要遍历
    //扣掉手上的牌
    //从此人牌中扣除
    const gangDict = getGangPlayDict(pai);
    if (!gangDict) { return; }

    const { paiarr, gangtype, isadd } = gangDict;
    // let gangPaiArr = pai.split(',');
    console.log(paiarr, paiarr.length, 'paiarr');
    let del_pai;
    if (!isadd) {
        // 不是加杠
        for (let i = 0; i < paiarr.length; i++) {
            const el = paiarr[i];
            let index = holds.indexOf(el);
            if (index == -1) {
                return;
            }
            console.log("扣除的牌", index, el);
            seatData.holds.splice(index, 1);
            seatData.countMap[el]--;
        }
    } else {
        // 加杠，只需扣重复的那个，去掉旧的杠
        if (teshugangs.length == 0) {
            return;
        }
        for (let i = 0; i < teshugangs.length; i++) {
            const el = teshugangs[i];
            let hasArr = el.split(',').map(e => +e);
            const ds = hasArr.filter(e => paiarr.indexOf(e) > -1); // 求交集
            if (ds && ds.length > 0) {
                //去掉旧的
                del_pai = seatData.teshugangs.splice(i, 1)
                seatData.discardPai = [
                    // ...seatData.discardPai,
                    ...del_pai,
                ];
                console.log(del_pai, seatData.discardPai, 'discard pai');
                // 比较多出来的pai
                let npai = [];
                paiarr.forEach(e => {
                    if (hasArr.includes(e)) {
                        hasArr.splice(hasArr.indexOf(e), 1);
                    } else {
                        npai.push(e);
                    }
                });
                // 只多一张加牌
                if (npai && npai.length === 1) {
                    let index = holds.indexOf(npai[0]);
                    console.log("扣除的牌11", index, npai[0]);
                    seatData.holds.splice(index, 1);
                    seatData.countMap[npai[0]]--;
                }
            }
        }
    }

    // 因为要记录，传到公共
    recordGameAction(game, seatData.seatIndex, ACTION_GANG, pai);

    if (gangtype == 'teshugang') {
        seatData.teshugangs.push(pai);
        game.teshugangs[pai] = game.teshugangs[pai] ? game.teshugangs[pai] += 1 : 1;
        var ac = recordUserAction(game, seatData, "teshugang");
        ac.score = game.conf.baseScore * 2;
    }

    console.log('扣掉牌之后手牌', seatData.holds, seatData.countMap, seatData, del_pai);
    // 牌算清后，验证以及下一个玩家
    checkCanTingPai(game, seatData);
    // 通知其他玩家，有人杠了牌
    userMgr.broacastInRoom('gang_notify_push', {
        userid: seatData.userId,
        pai: pai, // pai, // 摸到的最后的牌，lpai是去掉的最后一张牌，影响牌消失？
        gangtype: gangtype,
        discardPai: del_pai && del_pai.length ? del_pai[0] : undefined, // 只有一项
    }, seatData.userId, true);

    // 变成自己的轮子
    moveToNextUser(game, seatIndex);
    console.log('seat inx 当前inx', seatIndex, seatData.holds);
    // 再次摸牌
    // doUserMoPai(game);
    let len = paiarr.length;
    if (len < 4) {
        // 不用摸牌
        
        var numOfMJ = game.mahjongs.length - game.currentIndex;
        userMgr.broacastInRoom('mj_count_push', numOfMJ, turnSeat.userId, true);
        recordGameAction(game, game.turn, ACTION_MOPAI, pai);
        // 检查加杠？ 检查特殊杠
        checkSpecialGang(game, turnSeat);
        
        //广播通知玩家出牌方
        seatData.canChuPai = true;
        userMgr.broacastInRoom('game_chupai_push', seatData.userId, seatData.userId, true);
        //通知玩家做对应操作
        sendOperations(game, turnSeat, game.chuPai);
    } else {
        // 摸牌，补牌
        doSpecialBuPai(game);
    }

    //只能放在这里。因为过手就会清除杠牌标记
    seatData.lastFangGangSeat = gameTurn;
    // seatData.mhGang = false; // 标记修改
    // seatData.canGang = false;
}

function checkCanHu(game, seatData, pai, ignoreCheckFan) {
    game.lastHuPaiSeat = -1;
    // if (getMJType(pai) == seatData.que) {
    //     return;
    // }
    seatData.canHu = false;

    for (var k in seatData.tingMap) {
        if (pai == k) {
            seatData.canHu = true;
            break;
        }
    }

    // 可胡则直接返回
    if (seatData.canHu == true) {
        return true;
    }

    // 手上有会牌
    if (seatData.holds.includes(game.realhuipai)) {
        // 通过会牌验证
        const ret = mjutils.checkTingHuiPai(game, seatData, pai);
        if (ret.canhu) {
            seatData.useHuiPai = true;
            seatData.canHu = true;
            seatData.tingMap[game.realhuipai] = {
                pattern: "huipaihu",
                fan: 2,
            };
            if (ret.teshugangs && ret.teshugangs.length > 0) {
                seatData.teshugangs = [ ...seatData.teshugangs, ...ret.teshugangs ]
            }
        }
        console.log('check hu can hui', game.realhuipai, seatData.tingMap, seatData.canHu);
    }

    // // 不能没有1/9？
    // const haveTo = [0, 8, 9, 17, 18, 26];
    // const checkHolds = Object.keys(seatData.countMap).filter(e => {
    //     return haveTo.indexOf(e) > -1;
    // })
    // if (checkHolds.length === 0) {
    //     seatData.canHu = false;
    // }
}

function clearAllOptions(game, seatData) {
    var fnClear = function (sd) {
        sd.canPeng = false;
        sd.canChi = false;
        sd.canGang = false;
        sd.chiPai = [];
        sd.gangPai = [];
        sd.canHu = false;
        sd.lastFangGangSeat = -1;
    }
    if (seatData) {
        fnClear(seatData);
    } else {
        game.qiangGangContext = null;
        for (var i = 0; i < game.gameSeats.length; ++i) {
            fnClear(game.gameSeats[i]);
        }
    }
}

function clearContinuousGangs(game) {
    game.continuousGangs = 0;
}

function getTingList(sd) {
    var tings = [];
    for (var k in sd.tingMap) {
        var c = parseInt(k);
        tings.push(c);
    }

    sd.tings = tings;

    return tings;
};

//检查听牌
function checkCanTingPai(game, seatData) {
    seatData.tingMap = {};
    // var kou = seatData.kou;

    //检查是否是七对 前提是没有碰，也没有杠 ，即手上拥有13张牌
    // kou.length == 0 
    // 没碰或杠过
    if (seatData.holds.length == 13) {
        // 有5对牌
        var hu = false;
        var danPai = -1;
        var pairCount = 0;

        for (var k in seatData.countMap) {
            var c = seatData.countMap[k];
            if (c == 2 || c == 3) {
                pairCount++;
            } else if (c == 4) {
                pairCount += 2;
            }

            if (c == 1 || c == 3) {
                //如果已经有单牌了，表示不止一张单牌，直接闪
                if (danPai >= 0) {
                    break;
                }
                danPai = k;
            }
        }

        // 检查是否有6对 并且单牌是不是目标牌
        if (pairCount == 6) {
            // 七对只能和一张，就是手上那张单牌
            seatData.tingMap[danPai] = {
                pattern: "7pairs",
                fan: 2,
            };
        }

        // 没碰或杠，不能胡牌
        if (pairCount === 0) {
            return;
        }
    }

    //检查是否是对对胡
    //对对胡叫牌有两种情况
    //1、N坎 + 1张单牌
    //2、N-1坎 + 两对牌
    var singleCount = 0;
    var colCount = 0;
    var pairCount = 0;
    var arr = [];

    for (var k in seatData.countMap) {
        var c = seatData.countMap[k];
        if (c == 1) {
            singleCount++;
            arr.push(k);
        } else if (c == 2) {
            pairCount++;
            arr.push(k);
        } else if (c == 3) {
            colCount++;
        } else if (c == 4) {
            //手上有4个一样的牌，在四川麻将中是和不了对对胡的 随便加点东西
            singleCount++;
            pairCount += 2;
        }
    }

    if ((pairCount == 2 && singleCount == 0) || (pairCount == 0 && singleCount == 1)) {
        for (var i = 0; i < arr.length; ++i) {
            //对对胡1番
            var p = arr[i];
            if (seatData.tingMap[p] == null) {
                seatData.tingMap[p] = {
                    pattern: "duidui",
                    fan: 1,
                };
            }
        }
    }

    // 检查是不是平胡
    // 增加会牌
    mjutils.checkTingPai(seatData, 0, 9);
    mjutils.checkTingPai(seatData, 9, 18);
    mjutils.checkTingPai(seatData, 18, 27);
    mjutils.checkTingPai(seatData, 27, 34);
}

function checkCanChuPai(game, seatData, pai) {
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var sd = game.gameSeats[i];

        if (i == seatData.seatIndex) {
            continue;
        }

        if (sd.hasMingPai && sd.tings && sd.tings.indexOf(pai) >= 0) {
            return false;
        }
    }

    return true;
}

function checkCanMingPai(game, sd) {
    if (sd.hasMingPai) {
        return;
    }

    if (game.conf.type == 'xgkwx' && game.conf.chkming && sd.holds.length < 12) {
        return;
    }

    var oldmap = sd.tingMap;

    sd.mingPai = [];

    for (var k in sd.countMap) {
        var c = sd.countMap[k];
        var pai = parseInt(k);
        if (0 == c) {
            continue;
        }

        sd.countMap[k]--;

        checkCanTingPai(game, sd);

        if (isTinged(sd) && checkCanChuPai(game, sd, pai)) {
            sd.mingPai.push(pai)
        }

        sd.countMap[k] = c;
    }

    if (sd.mingPai.length > 0) {
        sd.canMingPai = true;
    }

    sd.tingMap = oldmap;
}

function getSeatIndex(userId) {
    var seatIndex = roomMgr.getUserSeatId(userId);
    if (seatIndex == null) {
        return null;
    }

    return seatIndex;
}

function getGameByUserID(userId) {
    var roomId = roomMgr.getUserRoom(userId);
    if (roomId == null) {
        return null;
    }

    var game = games[roomId];
    return game;
}

function hasOperations(seatData) {
    // return (seatData.canGang || seatData.canPeng || seatData.canHu || (!seatData.hasMingPai && seatData.canMingPai));
    // console.log(seatData.canChi, 'can chi');
    if (seatData.canGang || seatData.canPeng || seatData.canChi || seatData.canHu) {
        return true;
    }
    return false;
}

function sendOperations(game, seatData, pai) {
    console.log('send operation');
    if (hasOperations(seatData)) {
        if (pai == -1) {
            pai = seatData.holds[seatData.holds.length - 1];
        }

        var data = {
            pai: pai,
            hu: seatData.canHu,
            peng: seatData.canPeng,
            chi: seatData.canChi,
            gang: seatData.canGang,
            gangpai: seatData.gangPai,
            chipai: seatData.chiPai,
        };

        // 如果可以有操作，则进行操作
        userMgr.sendMsg(seatData.userId, 'game_action_push', data);

        data.si = seatData.seatIndex;
    } else {
        // , data
        userMgr.sendMsg(seatData.userId, 'game_action_push');
    }
}

function moveToNextUser(game, nextSeat) {
    game.fangpaoshumu = 0;
    //找到下一个没有和牌的玩家
    if (nextSeat == null) {
        while (true) {
            game.turn++;
            game.turn %= 4;
            var turnSeat = game.gameSeats[game.turn];
            if (turnSeat.hued == false) {
                return;
            }
        }
    } else {
        game.turn = nextSeat;
    }
}
function doSpecialBuPai(game) {
    game.chuPai = -1;
    var turnSeat = game.gameSeats[game.turn];
    turnSeat.lastFangGangSeat = -1;
    turnSeat.guoHuFan = -1;
    var pai = mopai(game, game.turn);
    //牌摸完了，结束
    if (pai == -1) {
        doGameOver(game, turnSeat.userId);
        return;
    } else {
        var numOfMJ = game.mahjongs.length - game.currentIndex;
        userMgr.broacastInRoom('mj_count_push', numOfMJ, turnSeat.userId, true);
    }

    recordGameAction(game, game.turn, ACTION_MOPAI, pai);

    //通知前端新摸的牌
    userMgr.sendMsg(turnSeat.userId, 'game_mopai_push', pai);
    //检查是否可以暗杠或者胡
    //检查胡，直杠，弯杠
    checkCanAnGang(game, turnSeat);
    checkCanWanGang(game, turnSeat, pai);
    //检查特殊杠
    checkSpecialGang(game, turnSeat);

    //检查看是否可以和
    checkCanHu(game, turnSeat, pai);

    //广播通知玩家出牌方
    turnSeat.canChuPai = true;
    userMgr.broacastInRoom('game_chupai_push', turnSeat.userId, turnSeat.userId, true);

    //通知玩家做对应操作
    sendOperations(game, turnSeat, game.chuPai);
}
// 摸牌
function doUserMoPai(game) {
    // 血战摸牌
    game.chuPai = -1;
    var turnSeat = game.gameSeats[game.turn];
    turnSeat.lastFangGangSeat = -1;
    turnSeat.guoHuFan = -1;
    var pai = mopai(game, game.turn);
    //牌摸完了，结束
    if (pai == -1) {
        doGameOver(game, turnSeat.userId);
        return;
    } else {
        var numOfMJ = game.mahjongs.length - game.currentIndex;
        userMgr.broacastInRoom('mj_count_push', numOfMJ, turnSeat.userId, true);
    }

    recordGameAction(game, game.turn, ACTION_MOPAI, pai);

    //通知前端新摸的牌
    userMgr.sendMsg(turnSeat.userId, 'game_mopai_push', pai);
    //检查是否可以暗杠或者胡
    //检查胡，直杠，弯杠
    checkCanAnGang(game, turnSeat);
    checkCanWanGang(game, turnSeat);
    //检查特殊杠
    checkSpecialGang(game, turnSeat);

    //检查看是否可以和
    checkCanHu(game, turnSeat, pai);

    //广播通知玩家出牌方
    turnSeat.canChuPai = true;
    userMgr.broacastInRoom('game_chupai_push', turnSeat.userId, turnSeat.userId, true);

    //通知玩家做对应操作
    sendOperations(game, turnSeat, game.chuPai);
}

// 牌型验证
function isSameType(type, arr) {
    for (var i = 0; i < arr.length; ++i) {
        var t = getMJType(arr[i]);
        if (type != -1 && type != t) {
            return false;
        }
        type = t;
    }
    return true;
}

function isQingYiSe(gameSeatData) {
    var type = getMJType(gameSeatData.holds[0]);

    //检查手上的牌
    if (isSameType(type, gameSeatData.holds) == false) {
        return false;
    }

    //检查杠下的牌
    if (isSameType(type, gameSeatData.angangs) == false) {
        return false;
    }
    if (isSameType(type, gameSeatData.wangangs) == false) {
        return false;
    }
    if (isSameType(type, gameSeatData.diangangs) == false) {
        return false;
    }

    //检查碰牌
    if (isSameType(type, gameSeatData.pengs) == false) {
        return false;
    }
    return true;
}

// reserved
function isMenQing(seatData) {
    return (seatData.pengs.length + seatData.wangangs.length + seatData.diangangs.length) == 0;
}

// reserved
function isZhongZhang(seatData) {
    var fn = function (arr) {
        for (var i = 0; i < arr.length; ++i) {
            var pai = arr[i];
            if (pai == 0 || pai == 8 || pai == 9 || pai == 17 || pai == 18 || pai == 26) {
                return false;
            }
        }
        return true;
    }

    if (fn(seatData.pengs) &&
        fn(seatData.angangs) &&
        fn(seatData.diangangs) &&
        fn(seatData.wangangs) &&
        fn(seatData.holds)) {
        return true;
    }

    return false;
}

//reserved
function isJiangDui(seatData) {
    var fn = function (arr) {
        for (var i = 0; i < arr.length; ++i) {
            var pai = arr[i];
            if (pai != 1 && pai != 4 && pai != 7 &&
                pai != 9 && pai != 13 && pai != 16 &&
                pai != 18 && pai != 21 && pai != 25) {
                return false;
            }
        }

        return true;
    }

    if (fn(seatData.pengs) &&
        fn(seatData.angangs) &&
        fn(seatData.diangangs) &&
        fn(seatData.wangangs) &&
        fn(seatData.holds)) {
        return true;
    }

    return false;
}

function isTinged(seatData) {
    // for (var k in seatData.tingMap) {
    //     return true;
    // }
    if (Object.keys(seatData.tingMap) && Object.keys(seatData.tingMap).length > 0) {
        return true;
    }

    return false;
}

function computeFanScore(game, fan) {
    // if (fan > game.conf.maxFan) {
    //     fan = game.conf.maxFan;
    // }
    console.log('jisuan fan max?', (1 << fan) * game.baseScore);
    return (1 << fan) * game.baseScore;
}

//是否需要查大叫(有两家以上未胡，且有人没有下叫)
function needChaDaJiao(game) {
    //查叫
    var numOfHued = 0;
    var numOfTinged = 0;
    var numOfUntinged = 0;
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var ts = game.gameSeats[i];
        if (ts.hued) {
            numOfHued++;
            numOfTinged++;
        } else if (isTinged(ts)) {
            numOfTinged++;
        } else {
            numOfUntinged++;
        }
    }

    //如果三家都胡牌了，不需要查叫
    if (numOfHued == 3) {
        return false;
    }

    //如果没有任何一个人叫牌，也没有任何一个胡牌，则不需要查叫
    if (numOfTinged == 0) {
        return false;
    }

    //如果都听牌了，也不需要查叫
    if (numOfUntinged == 0) {
        return false;
    }
    return true;
}

function findMaxFanTingPai(ts) {
    //找出最大番
    var cur = null;
    for (var k in ts.tingMap) {
        var tpai = ts.tingMap[k];
        if (cur == null || tpai.fan > cur.fan) {
            cur = tpai;
            // cur.pai = parseInt(k);
        }
    }

    return cur;
}

// no use
function findUnTingedPlayers(game) {
    var arr = [];
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var ts = game.gameSeats[i];
        //如果没有胡，且没有听牌
        if (!ts.hued && !isTinged(ts)) {
            arr.push(i);
            recordUserAction(game, ts, "beichadajiao", -1);
        }
    }
    return arr;
}

function getNumOfGen(seatData) {
    var numOfGangs = seatData.diangangs.length + seatData.wangangs.length + seatData.angangs.length;

    for (var k = 0; k < seatData.pengs.length; ++k) {
        var pai = seatData.pengs[k];
        if (seatData.countMap[pai] == 1) {
            numOfGangs++;
        }
    }

    for (var k in seatData.countMap) {
        if (seatData.countMap[k] == 4) {
            numOfGangs++;
        }
    }

    return numOfGangs;
}

function getFan(game, sd, pai) {
    var info = sd.tingMap[pai];
    var type = game.conf.type;

    sd.holds.push(pai);
    if (sd.countMap[pai] != null) {
        sd.countMap[pai]++;
    } else {
        sd.countMap[pai] = 1;
    }

    var qingyise = mjutils.checkQingYiSe(sd) || null;
    var isJinGouHu = (sd.holds.length == 1 || sd.holds.length == 2) || null;
    var fan = info.fan;

    if (qingyise) {
        fan += 2;
    }

    if (isJinGouHu) {
        fan += 2;
    }

    if (info.pattern == '7pairs') {
        var dragon = mjutils.calcHoldMultiCardNum(sd, 4);
        var sanyuan7pairs = mjutils.checkSanYuan7Pairs(sd);

        if (3 == dragon) {
            fan += 5;
        } else if (2 == dragon) {
            fan += 3;
        } else if (sanyuan7pairs) {
            fan += 3;
        } else if (1 == dragon) {
            fan += 1;
        }
    } else {
        if (mjutils.checkDaSanYuan(sd)) {
            fan += 3;
        } else if (mjutils.checkXiaoSanYuan(sd)) {
            fan += 2;
        }

        if (mjutils.checkKaWuXing(sd)) {
            if (type == 'sykwx') {
                fan += 2;
            } else {
                fan += 1;
            }
        }

        if (mjutils.checkMingSiGui(game, sd)) {
            fan += 1;
        } else if (mjutils.checkAnSiGui(game, sd)) {
            fan += 2;
        }
    }

    sd.holds.pop();
    sd.countMap[pai]--;

    // if (fan > game.conf.maxFan) {
    //     fan = game.conf.maxFan;
    // }

    return computeFanScore(game, fan);
}

function getMaxFan(game, sd) {
    var maxFan = 0;
    for (var k in sd.tingMap) {
        var fan = getFan(game, sd, parseInt(k));

        if (fan > maxFan) {
            maxFan = fan;
        }
    }

    return maxFan;
}

function supportHaidi(game) {
    var conf = game.conf;
    return (conf.type == 'xykwx' && conf.pindao == 0) || conf.type == 'sykwx';
}

/////----------------------------------

//是否需要查大叫(有两家以上未胡，且有人没有下叫)
function needChaDaJiao(game) {
    //查叫
    var numOfHued = 0;
    var numOfTinged = 0;
    var numOfUntinged = 0;
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var ts = game.gameSeats[i];
        if (ts.hued) {
            numOfHued++;
            numOfTinged++;
        }
        else if (isTinged(ts)) {
            numOfTinged++;
        }
        else {
            numOfUntinged++;
        }
    }

    //如果三家都胡牌了，不需要查叫
    if (numOfHued == 3) {
        return false;
    }

    //如果没有任何一个人叫牌，也没有任何一个胡牌，则不需要查叫
    if (numOfTinged == 0) {
        return false;
    }

    //如果都听牌了，也不需要查叫
    if (numOfUntinged == 0) {
        return false;
    }
    return true;
}

function findMaxFanTingPai(ts) {
    //找出最大番
    var cur = null;
    for (var k in ts.tingMap) {
        var tpai = ts.tingMap[k];
        if (cur == null || tpai.fan > cur.fan) {
            cur = tpai;
        }
    }
    return cur;
}

function findUnTingedPlayers(game) {
    var arr = [];
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var ts = game.gameSeats[i];
        //如果没有胡，且没有听牌
        if (!ts.hued && !isTinged(ts)) {
            arr.push(i);
            recordUserAction(game, ts, "beichadajiao", -1);
        }
    }
    return arr;
}

function chaJiao(game) {
    var arr = findUnTingedPlayers(game);
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var ts = game.gameSeats[i];
        //如果没有胡，但是听牌了，则未叫牌的人要给钱
        if (!ts.hued && isTinged(ts)) {
            var cur = findMaxFanTingPai(ts);
            ts.fan = cur.fan;
            ts.pattern = cur.pattern;
            recordUserAction(game, ts, "chadajiao", arr);
        }
    }
}
/////------------------------------------
/**
 * 杠小分计算 
 * @param gangs (angangs, diangangs, wangangs) 杠牌数据
 * @param isAnGang 是否是暗杠数据
 * 例: calcAnGangFen(seatData.diangangs, false)
 */
calcGangFen = function (gangs, isAnGang, game) {
    // 这里看下中发白的暗杠 服务端是怎么存储的
    var totalScore = 0; // 总分
    for (var i = 0; i < gangs.length; i ++) {
        // 会牌 改成服务端会牌字段
        var huipai = game.realhuipai;
        var pai = gangs[i];
        // 会杠
        if (pai == huipai) {
            totalScore += 16;
        // 二 八杠 2筒 8万
        } else if (pai == 1 || pai == 10 || pai == 19 || pai == 7 || pai == 16 || pai == 25) {
            totalScore += 8;
        // 幺 九 杠 以及中发白 这里就当中发白杠 只推进数组其中一张牌
        } else if (pai == 0 || pai == 9 || pai == 18 || pai == 8 || pai == 17 || pai == 26 || (pai <= 29 && pai >= 27)) {
            totalScore += 4;
        } else {
            totalScore += 2;
        }
    }
    return isAnGang ? totalScore : totalScore / 2;
}

function calculateResult(game, roomInfo) {
    // var isNeedChaDaJia = needChaDaJiao(game);
    // if (isNeedChaDaJia) {
    //     chaJiao(game);
    // }

    var baseScore = game.conf.baseScore;
    var numOfHued = 0;

    ///////------旧版----------------
    for (var i = 0; i < game.gameSeats.length; ++i) {
        if (game.gameSeats[i].hued == true) {
            numOfHued++;
        }
    }
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var sd = game.gameSeats[i]; // seatData

        //统计杠的数目
        sd.numAnGang = sd.angangs.length;
        sd.numMingGang = sd.wangangs.length + sd.diangangs.length + sd.teshugangs.length;

        console.log('seat inx', i, sd.fan);
        //对所有胡牌的玩家进行统计
        if (isTinged(sd)) {
            //统计自己的番子和分数
            //基础番(平胡0番，对对胡1番、七对2番) + 清一色2番 + 杠+1番
            //杠上花+1番，杠上炮+1番 抢杠胡+1番，金钩胡+1番，海底胡+1番
            var fan = sd.fan;
            // if (isQingYiSe(sd)) {
            //     sd.qingyise = true;
            //     fan += 2;
            // }

            var numOfGangs = sd.diangangs.length + sd.wangangs.length + sd.angangs.length + sd.teshugangs.length;
            for (var j = 0; j < sd.pengs.length; ++j) {
                var pai = sd.pengs[j];
                if (sd.countMap[pai] == 1) {
                    numOfGangs++;
                }
            }
            for (var k in sd.countMap) {
                if (sd.countMap[k] == 4) {
                    numOfGangs++;
                }
            }
            sd.numofgen = numOfGangs;

            //金钩胡
            if (sd.holds.length == 1 || sd.holds.length == 2) {
                fan += 1;
                sd.isJinGouHu = true;
            }

            // 底番为1
            if (sd.isHaiDiHu) {
                fan += 1;
            }

            if (game.conf.tiandihu) {
                // 天胡为8
                if (sd.isTianHu) {
                    fan = 8;
                }
                // 地胡为4
                else if (sd.isDiHu) {
                    fan = 4;
                }
            }

            var isjiangdui = false;
            if (game.conf.jiangdui) {
                if (sd.pattern == "7pairs") {
                    if (sd.numofgen > 0) {
                        sd.numofgen -= 1;
                        sd.pattern == "l7pairs";
                        isjiangdui = isJiangDui(sd);
                        if (isjiangdui) {
                            sd.pattern == "j7paris";
                            fan += 2;
                        }
                        else {
                            fan += 1;
                        }
                    }
                } else if (sd.pattern == "duidui") {
                    isjiangdui = isJiangDui(sd);
                    if (isjiangdui) {
                        sd.pattern = "jiangdui";
                        fan += 2;
                    }
                }
            }

            if (game.conf.menqing) {
                //不是将对，才检查中张
                if (!isjiangdui) {
                    sd.isZhongZhang = isZhongZhang(sd);
                    if (sd.isZhongZhang) {
                        fan += 1;
                    }
                }

                sd.isMenQing = isMenQing(sd);
                if (sd.isMenQing) {
                    fan += 1;
                }
            }

            fan += sd.numofgen;
            if (sd.isGangHu) {
                fan += 1;
            }
            if (sd.isQiangGangHu) {
                fan += 1;
            }

            // 收杠钱
            var additonalscore = 0;
            for (var a = 0; a < sd.actions.length; ++a) {
                var ac = sd.actions[a];
                if (ac.type == "fanggang") {
                    var ts = game.gameSeats[ac.targets[0]];
                    // 检查放杠的情况，如果目标没有和牌，且没有叫牌，则不算 用于优化前端显示
                    // isNeedChaDaJia && 
                    if ((ts.hued) == false && (isTinged(ts) == false)) {
                        ac.state = "nop";
                    }
                } else if (
                    ac.type == "angang" ||
                    ac.type == "wangang" ||
                    ac.type == "diangang" ||
                    ac.type == 'teshugang'
                ) {
                    if (ac.state != "nop") {
                        var acscore = ac.score;
                        additonalscore += ac.targets.length * acscore * baseScore;
                        // 扣掉目标方的分
                        for (var t = 0; t < ac.targets.length; ++t) {
                            var six = ac.targets[t];
                            game.gameSeats[six].score -= acscore * baseScore;
                        }
                    }
                } else if (ac.type == "maozhuanyu") {
                    // 对于呼叫转移，如果对方没有叫牌，表示不得行
                    if (isTinged(ac.owner)) {
                        //如果
                        var ref = ac.ref;
                        var acscore = ref.score;
                        var total = ref.targets.length * acscore * baseScore;
                        additonalscore += total;
                        //扣掉目标方的分
                        if (ref.payTimes == 0) {
                            for (var t = 0; t < ref.targets.length; ++t) {
                                var six = ref.targets[t];
                                game.gameSeats[six].score -= acscore * baseScore;
                            }
                        } else {
                            //如果已经被扣过一次了，则由杠牌这家赔
                            ac.owner.score -= total;
                        }
                        ref.payTimes++;
                        ac.owner = null;
                        ac.ref = null;
                    }
                } else if (
                    ac.type == "zimo" || // 自摸
                    ac.type == "hu" || // 放炮？
                    ac.type == "ganghua" || // 杠上花
                    ac.type == "dianganghua" ||
                    ac.type == "gangpaohu" || // 杠点炮
                    ac.type == "qiangganghu" ||
                    ac.type == "chadajiao"
                ) {
                    var extraScore = 0;
                    if (ac.iszimo) {
                        if (game.conf.zimo == 0) {
                            // 自摸加底
                            extraScore = baseScore;
                        }
                        if (game.conf.zimo == 1) {
                            // fan += 1;
                            fan *= 2;
                        } else {
                            //nothing.
                        }
                        sd.numZiMo++;
                    } else {
                        if (ac.type != "chadajiao") {
                            sd.numJiePao++;
                        }
                    }
                    var score = computeFanScore(game, fan) + extraScore;
                    console.log('胡牌家番-----', score, extraScore, ac.targets);
                    sd.score += score * ac.targets.length;

                    for (var t = 0; t < ac.targets.length; ++t) {
                        var six = ac.targets[t];
                        var td = game.gameSeats[six];
                        td.score -= score;
                        if (td != sd) {
                            if (ac.type == "chadajiao") {
                                td.numChaJiao++;
                            } else if (!ac.iszimo) {
                                td.numDianPao++;
                            }
                        }
                    }
                }
            }

            // 穷胡，没有会牌
            if (sd.countMap[game.realhuipai] == 0) {
                fan *= 2;
            }

            // if (fan > game.conf.maxFan) {
            //     fan = game.conf.maxFan;
            // }
            console.log(additonalscore, '----累加番数----');
            // 一定要用 += 因为此时的sd.score可能是负的
            sd.score += additonalscore;
            if (sd.pattern != null) {
                sd.fan = fan;
            }
            console.log('after jisuan', sd.pattern, sd.fan, sd.score);
        } else {
            for (var a = sd.actions.length - 1; a >= 0; --a) {
                var ac = sd.actions[a];
                if (ac.type == "angang" ||
                    ac.type == "wangang" ||
                    ac.type == "diangang" ||
                    ac.type == 'teshugang') {
                    // 如果3家都胡牌，则需要结算。否则认为是查叫
                    // if (numOfHued < 3) {
                    //     sd.actions.splice(a, 1);
                    // } else {
                    if (ac.state != "nop") {
                        var acscore = ac.score;
                        console.log(acscore, '----番数----', baseScore, '---基础---', ac.targets, '==target==');
                        sd.score += ac.targets.length * acscore * baseScore;
                        // 扣掉目标方的分
                        for (var t = 0; t < ac.targets.length; ++t) {
                            var six = ac.targets[t];
                            game.gameSeats[six].score -= acscore * baseScore;
                        }
                    }
                    // }
                }
            }
        }
    }
}

function getRoomInfo(uid) {
    var roomId = roomMgr.getUserRoom(uid);
    if (roomId == null) {
        return null;
    }

    return roomMgr.getRoom(roomId);
}

function doGameOver(game, userId, forceEnd) {
    var roomId = roomMgr.getUserRoom(userId);
    if (roomId == null) {
        return;
    }
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return;
    }

    var results = [];
    var dbresult = [0, 0, 0, 0];

    var fnNoticeResult = function (isEnd) {
        var endinfo = null;
        if (isEnd) {
            endinfo = [];
            for (var i = 0; i < roomInfo.seats.length; ++i) {
                var rs = roomInfo.seats[i];
                endinfo.push({
                    numzimo: rs.numZiMo,
                    numjiepao: rs.numJiePao,
                    numdianpao: rs.numDianPao,
                    numangang: rs.numAnGang,
                    numminggang: rs.numMingGang,
                    numchadajiao: rs.numChaJiao,
                });
            }
        }
        userMgr.broacastInRoom('game_over_push', { results: results, endinfo: endinfo }, userId, true);
        //如果局数已够，则进行整体结算，并关闭房间
        if (isEnd) {
            setTimeout(function () {
                if (roomInfo.numOfGames > 1) {
                    store_history(roomInfo);
                }

                userMgr.kickAllInRoom(roomId);
                roomMgr.destroy(roomId);
                db.archive_games(roomInfo.uuid);
            }, 1500);
        }
    }

    if (game != null) {
        if (!forceEnd) {
            calculateResult(game, roomInfo);
        }

        for (var i = 0; i < roomInfo.seats.length; ++i) {
            var rs = roomInfo.seats[i];
            var sd = game.gameSeats[i];

            rs.ready = false;
            rs.score += sd.score;
            rs.numZiMo += sd.numZiMo;
            rs.numJiePao += sd.numJiePao;
            rs.numDianPao += sd.numDianPao;
            rs.numAnGang += sd.numAnGang;
            rs.numMingGang += sd.numMingGang;
            rs.numChaJiao += sd.numChaJiao;

            var userRT = {
                userId: sd.userId,
                pengs: sd.pengs,
                chis: sd.chis,
                actions: [],
                wangangs: sd.wangangs,
                diangangs: sd.diangangs,
                angangs: sd.angangs,
                teshugangs: sd.teshugangs,
                discardPai: sd.discardPai,
                numofgen: sd.numofgen,
                holds: sd.holds,
                fan: sd.fan,
                score: sd.score,
                totalscore: rs.score,
                qingyise: sd.qingyise,
                pattern: sd.pattern,
                isganghu: sd.isGangHu,
                menqing: sd.isMenQing,
                zhongzhang: sd.isZhongZhang,
                jingouhu: sd.isJinGouHu,
                haidihu: sd.isHaiDiHu,
                tianhu: sd.isTianHu,
                dihu: sd.isDiHu,
                huorder: game.hupaiList.indexOf(i),
                useHuiPai: sd.useHuiPai, // 用会牌胡
            };

            for (var k in sd.actions) {
                userRT.actions[k] = {
                    type: sd.actions[k].type,
                };
            }
            results.push(userRT);


            dbresult[i] = sd.score;
            delete gameSeatsOfUsers[sd.userId];
        }
        delete games[roomId];

        var old = roomInfo.nextButton;
        if (game.yipaoduoxiang >= 0) {
            roomInfo.nextButton = game.yipaoduoxiang;
        } else if (game.firstHupai >= 0) {
            roomInfo.nextButton = game.firstHupai;
        } else {
            roomInfo.nextButton = (game.turn + 1) % 4;
        }

        if (old != roomInfo.nextButton) {
            db.update_next_button(roomId, roomInfo.nextButton);
        }
    }

    if (forceEnd || game == null) {
        fnNoticeResult(true);
    } else {
        //保存游戏
        store_game(game, function (ret) {

            db.update_game_result(roomInfo.uuid, game.gameIndex, dbresult);

            //记录打牌信息
            var str = JSON.stringify(game.actionList);
            db.update_game_action_records(roomInfo.uuid, game.gameIndex, str);

            //保存游戏局数
            db.update_num_of_turns(roomId, roomInfo.numOfGames);

            //如果是第一次，并且不是强制解散 则扣除房卡
            if (roomInfo.numOfGames == 1) {
                var cost = 2;
                if (roomInfo.conf.maxGames == 8) {
                    cost = 3;
                }
                db.cost_gems(game.gameSeats[0].userId, cost);
            }

            var isEnd = (roomInfo.numOfGames >= roomInfo.conf.maxGames);
            fnNoticeResult(isEnd);
        });
    }
}

function recordUserAction(game, seatData, type, target) {
    var d = { type: type, targets: [] };
    if (target != null) {
        if (typeof (target) == 'number') {
            d.targets.push(target);
        } else {
            d.targets = target;
        }
    } else {
        for (var i = 0; i < game.gameSeats.length; ++i) {
            var s = game.gameSeats[i];
            if (i != seatData.seatIndex && s.hued == false) {
                d.targets.push(i);
            }
        }
    }
    // console.log('record user', d);
    seatData.actions.push(d);
    console.log('-----------record action---------------', seatData.actions);
    return d;
}

function recordGameAction(game, si, action, pai) {
    game.actionList.push(si);
    game.actionList.push(action);
    if (pai != null) {
        game.actionList.push(pai);
    }
}

function store_single_history(userId, history) {
    db.get_user_history(userId, function (data) {
        if (data == null) {
            data = [];
        }

        while (data.length >= 10) {
            data.shift();
        }

        data.push(history);
        db.update_user_history(userId, data);
    });
}

function store_history(roomInfo) {
    var seats = roomInfo.seats;
    var history = {
        uuid: roomInfo.uuid,
        id: roomInfo.id,
        time: roomInfo.createTime,
        seats: new Array(4) // new Array(exports.numOfSeats)
    };

    for (var i = 0; i < seats.length; ++i) {
        var rs = seats[i];
        var hs = history.seats[i] = {};
        hs.userid = rs.userId;
        hs.name = crypto.toBase64(rs.name);
        hs.score = rs.score;
    }

    for (var i = 0; i < seats.length; ++i) {
        var s = seats[i];
        store_single_history(s.userId, history);
    }
}

function construct_game_base_info(game) {
    var numOfSeats = game.numOfSeats;
    var baseInfo = {
        type: game.conf.type,
        button: game.button,
        index: game.gameIndex,
        mahjongs: game.mahjongs,
        game_seats: new Array(numOfSeats),
        conf: game.conf,
    }

    for (var i = 0; i < numOfSeats; ++i) {
        baseInfo.game_seats[i] = game.gameSeats[i].holds;
    }

    game.baseInfoJson = JSON.stringify(baseInfo);
}

function store_game(game, callback) {
    db.create_game(game.roomInfo.uuid, game.gameIndex, game.baseInfoJson, callback);
}

function checkCanQiangGang(game, turnSeat, seatData, pai) {
    var hasActions = false;
    for (var i = 0; i < game.gameSeats.length; ++i) {
        //杠牌者不检查
        if (seatData.seatIndex == i) {
            continue;
        }
        var gs = game.gameSeats[i];
        //已经和牌的不再检查
        if (gs.hued) {
            continue;
        }

        // var gs = game.gameSeats[i];
        checkCanHu(game, gs, pai, true);
        if (gs.canHu) {
            sendOperations(game, gs, pai);
            hasActions = true;
        }
    }

    if (hasActions) {
        game.qiangGangContext = {
            turnSeat: turnSeat,
            seatData: seatData,
            pai: pai,
            isValid: true,
        }
    } else {
        game.qiangGangContext = null;
    }

    return game.qiangGangContext != null;
}

function doGang(game, turnSeat, seatData, gangtype, numOfCnt, pai) {
    var seatIndex = seatData.seatIndex;
    var gameTurn = turnSeat.seatIndex;

    var isZhuanShouGang = false;
    if (gangtype == "wangang") {
        var idx = seatData.pengs.indexOf(pai);
        if (idx >= 0) {
            seatData.pengs.splice(idx, 1);
        }

        //如果最后一张牌不是杠的牌，则认为是转手杠
        if (seatData.holds[seatData.holds.length - 1] != pai) {
            isZhuanShouGang = true;
        }
    }
    //进行碰牌处理
    //扣掉手上的牌
    //从此人牌中扣除
    for (var i = 0; i < numOfCnt; ++i) {
        var index = seatData.holds.indexOf(pai);
        if (index == -1) {
            console.log(seatData.holds);
            console.log("can't find mj.");
            return;
        }
        seatData.holds.splice(index, 1);
        seatData.countMap[pai]--;
    }

    recordGameAction(game, seatData.seatIndex, ACTION_GANG, pai);

    //记录下玩家的杠牌
    if (gangtype == "angang") {
        seatData.angangs.push(pai);
        var ac = recordUserAction(game, seatData, "angang");
        ac.score = game.conf.baseScore * 2;
    } else if (gangtype == "diangang") {
        seatData.diangangs.push(pai);
        var ac = recordUserAction(game, seatData, "diangang", gameTurn);
        ac.score = game.conf.baseScore * 2;
        var fs = turnSeat;
        recordUserAction(game, fs, "fanggang", seatIndex);
    } else if (gangtype == "wangang") {
        seatData.wangangs.push(pai);
        if (isZhuanShouGang == false) {
            var ac = recordUserAction(game, seatData, "wangang");
            ac.score = game.conf.baseScore;
        } else {
            recordUserAction(game, seatData, "zhuanshougang");
        }
    }

    checkCanTingPai(game, seatData);
    //通知其他玩家，有人杠了牌
    console.log('普通杠对其他玩家的告知', { userid: seatData.userId, pai: pai, gangtype: gangtype });
    userMgr.broacastInRoom('gang_notify_push', {
        userid: seatData.userId,
        pai: pai,
        gangtype: gangtype
    }, seatData.userId, true);

    //变成自己的轮子
    moveToNextUser(game, seatIndex);
    //再次摸牌
    doUserMoPai(game);

    //只能放在这里。因为过手就会清除杠牌标记
    seatData.lastFangGangSeat = gameTurn;
}

beginChecking = function (userId, numOfQue, state) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        console.log("can't find user game data.");
        return;
    }

    var game = seatData.game;
    if (game.state != "checking") {
        console.log("can't recv dingQue when game.state == " + game.state);
        return;
    }

    // var seatData = gameSeatsOfUsers[userId];
    if (numOfQue + 1 == 4) {
        construct_game_base_info(game);
        // var arr = [1, 1, 1, 1];
        // for (var i = 0; i < game.gameSeats.length; ++i) {
        //     arr[i] = game.gameSeats[i].que;
        // }
        // userMgr.broacastInRoom('game_dingque_finish_push', arr, seatData.userId, true);
        userMgr.broacastInRoom('game_playing_push', null, seatData.userId, true);
        // 进行听牌检查
        for (var i = 0; i < game.gameSeats.length; ++i) {
            var duoyu = -1;
            var gs = game.gameSeats[i]; // turnSeat
            if (gs.holds.length == 14) {
                duoyu = gs.holds.pop();
                gs.countMap[duoyu] -= 1;
            }
            checkCanTingPai(game, gs);
            if (duoyu >= 0) {
                gs.holds.push(duoyu);
                gs.countMap[duoyu]++;
            }
        }

        var turnSeat = game.gameSeats[game.turn];
        game.state = "playing";
        //通知玩家出牌方
        turnSeat.canChuPai = true;
        userMgr.broacastInRoom('game_chupai_push', turnSeat.userId, turnSeat.userId, true);
        //检查是否可以暗杠或者胡
        checkCanAnGang(game, turnSeat);
        //检查特殊杠
        checkSpecialGang(game, turnSeat);

        //检查胡 用最后一张来检查
        checkCanHu(game, turnSeat, turnSeat.holds[turnSeat.holds.length - 1]);

        // checkCanMingPai(game, turnSeat);
        //通知前端
        sendOperations(game, turnSeat, game.chuPai);
    } else {
        // userMgr.broacastInRoom('game_dingque_notify_push', seatData.userId, seatData.userId, true);
    }

};

function update() {
    for (var i = dissolvingList.length - 1; i >= 0; --i) {
        var roomId = dissolvingList[i];

        var roomInfo = roomMgr.getRoom(roomId);
        if (roomInfo != null && roomInfo.dr != null) {
            if (Date.now() > roomInfo.dr.endTime) {
                console.log("delete room and games");
                exports.doDissolve(roomId);
                dissolvingList.splice(i, 1);
            }
        } else {
            dissolvingList.splice(i, 1);
        }
    }
}

// exports 开始
exports.numOfSeats = 4;
exports.setReady = function (userId, callback) {
    var roomId = roomMgr.getUserRoom(userId);
    var numOfSeats = exports.numOfSeats;
    if (roomId == null) {
        return;
    }

    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return;
    }
    console.log(userId, '---------ready in room------', roomId, '-----------------', roomInfo);

    roomMgr.setReady(userId, true);

    var game = games[roomId];
    // console.log(game, 'ready game');
    if (!game) {
        if (roomInfo.seats.length == numOfSeats) {
            for (var i = 0; i < roomInfo.seats.length; ++i) {
                var s = roomInfo.seats[i];
                if (!s.ready || !userMgr.isOnline(s.userId)) {
                    return;
                }
            }

            //人到齐了，并且都准备好了，则开始新的一局
            setTimeout(() => {
                exports.begin(roomId);
            }, 800);
        }
    } else {
        var numOfMJ = game.mahjongs.length - game.currentIndex;
        var remainingGames = roomInfo.conf.maxGames - roomInfo.numOfGames;

        var data = {
            state: game.state,
            numofmj: numOfMJ,
            numOfSeats: numOfSeats,
            button: game.button,
            turn: game.turn,
            chuPai: game.chuPai,
            huipai: game.huipai,
            realhuipai: game.realhuipai,
        };

        data.seats = [];
        var seatData = null;
        for (var i = 0; i < numOfSeats; ++i) {
            var sd = game.gameSeats[i];

            var s = {
                userid: sd.userId,
                folds: sd.folds,
                angangs: sd.angangs,
                diangangs: sd.diangangs,
                wangangs: sd.wangangs,
                teshugangs: sd.teshugangs,
                discardPai: sd.discardPai,
                // fenggangs: sd.fenggangs,
                // zfbgangs: sd.zfbgangs,
                // yaogangs: sd.yaogangs,
                // jiugangs: sd.jiugangs,
                pengs: sd.pengs,
                chis: sd.chis,
                // que: sd.que,
                hued: sd.hued,
                huinfo: sd.huInfo,
                iszimo: sd.iszimo,
                tings: sd.tings,
                mingpai: sd.hasMingPai,
                // kou: sd.kou,
                dingpiao: sd.dingpiao,
                useHuiPai: sd.useHuiPai,
            };

            if (sd.userId == userId) {
                s.holds = sd.holds;
                seatData = sd;
            } else if (sd.hasMingPai) {
                s.holds = sd.holds;
            }

            data.seats.push(s);
        }

        // 同步整个信息给客户端
        userMgr.sendMsg(userId, 'game_sync_push', data);
        // shuffle(game);
        sendOperations(game, seatData, game.chuPai);
    }
}
//开始新的一局
exports.begin = function (roomId) {
    var roomInfo = roomMgr.getRoom(roomId);
    console.log('room begin info ----------', roomInfo);
    if (roomInfo == null) {
        return;
    }

    var seats = roomInfo.seats;
    var numOfSeats = exports.numOfSeats; // 4人麻将
    var yuPai = 108;

    var game = {
        conf: roomInfo.conf,
        roomInfo: roomInfo,
        gameIndex: roomInfo.numOfGames,

        button: roomInfo.nextButton,
        mahjongs: new Array(yuPai), // new Array(84), //84张牌是为啥
        currentIndex: 0,
        numOfSeats: numOfSeats,
        gameSeats: new Array(numOfSeats),

        // 已经特殊杠的牌，全局记录，每局清空
        teshugangs: {},

        numOfQue: 0,
        turn: 0,
        chuPai: -1,
        state: "idle",
        firstHupai: -1,
        yipaoduoxiang: -1,
        fangpaoshumu: -1,
        actionList: [],
        hupaiList: [],
        chupaiCnt: 0,

        continuousGangs: 0,
        firstMingPai: -1,

        baseScore: roomInfo.conf.baseScore,

        dices: [],
        // 会牌
        huipai: -1,
    };

    // console.log((game.conf.up && roomInfo.nextUp != null), 'game.conf.up && roomInfo.nextUp != null');
    // if (game.conf.up && roomInfo.nextUp != null) {
    //     game.baseScore *= (1 << roomInfo.nextUp);
    // }

    roomInfo.numOfGames++;

    for (var i = 0; i < numOfSeats; ++i) {
        var data = game.gameSeats[i] = {};
        data.game = game;
        data.seatIndex = i;
        data.userId = seats[i].userId;

        //持有的牌
        data.holds = [];
        //打出的牌
        data.folds = [];
        //暗杠的牌
        data.angangs = [];
        //点杠的牌
        data.diangangs = [];
        //弯杠的牌
        data.wangangs = [];
        //碰了的牌
        data.pengs = [];
        //吃了的牌
        data.chis = [];
        // 特殊杠
        // data.fenggangs = [];
        // data.zfbgangs = [];
        // data.yaogangs = [];
        // data.jiugangs = [];
        data.teshugangs = [];
        // 丢弃的杠牌
        data.discardPai = [];

        //玩家手上的牌的数目，用于快速判定碰杠
        data.countMap = {};
        //玩家听牌，用于快速判定胡了的番数
        data.tingMap = {};
        data.pattern = "";

        //是否可以杠
        data.canGang = false;
        //用于记录玩家可以杠的牌
        data.gangPai = [];
        //记录可以吃的pai
        data.chiPai = [];

        //是否可以碰
        data.canPeng = false;
        //是否可以吃
        data.canChi = false;
        //是否可以胡
        data.canHu = false;
        //是否可以出牌
        data.canChuPai = false;

        //如果guoHuFan >=0 表示处于过胡状态，
        //如果过胡状态，那么只能胡大于过胡番数的牌
        data.guoHuFan = -1;

        //是否胡了
        data.hued = false;
        //
        data.actions = [];

        //是否是自摸
        data.iszimo = false;
        data.isGangHu = false;
        data.fan = 0;
        data.score = 0;
        data.huInfo = [];

        data.lastFangGangSeat = -1;

        //统计信息
        data.numZiMo = 0;
        data.numJiePao = 0;
        data.numDianPao = 0;
        data.numAnGang = 0;
        data.numMingGang = 0;

        data.hasMingPai = false;
        data.firstMingPai = false;
        data.tings = [];
        // data.kou = [];

        data.useHuiPai = false;
        data.hu = {};
        data.detail = {
            tips: null,
            piao: 0,
            gang: 0,
            fan: 0,
            score: 0,
        };

        data.dingpiao = seats[i].dingpiao;

        gameSeatsOfUsers[data.userId] = data;
    }

    games[roomId] = game;

    console.log(games, 'begin game or restart game info');

    if (gmFnDict.type === 'CALL_GM' && gmFnDict.fn === '1') {
        shuffle(game, gmFnDict.type);
    } else {
        shuffle(game);
    }
    // dice(game);
    //发牌
    deal(game);

    // game.huipai = 
    // 开局会牌
    // game.huipai = game.mahjongs[game.currentIndex];
    console.log('开局会牌', game.currentIndex, game.huipai, '真正的会牌', game.realhuipai);

    var numOfMJ = game.mahjongs.length - game.currentIndex;
    // var huansanzhang = roomInfo.conf.hsz;

    // 检查玩家可以做的动作
    // 通知庄家出牌
    for (var i = 0; i < seats.length; ++i) {
        //开局时，通知前端必要的数据
        var s = seats[i];

        // 通知会牌
        userMgr.sendMsg(s.userId, 'set_huipai', game.huipai);
        // 通知玩家手牌
        userMgr.sendMsg(s.userId, 'game_holds_push', game.gameSeats[i].holds);
        // 通知还剩多少张牌
        userMgr.sendMsg(s.userId, 'mj_count_push', numOfMJ);
        // 通知还剩多少局
        userMgr.sendMsg(s.userId, 'game_num_push', roomInfo.numOfGames);
        // 通知游戏开始
        userMgr.sendMsg(s.userId, 'game_begin_push', game.button);

        game.state = "checking";
        // 通知准备判定特殊杠
        // userMgr.sendMsg(s.userId, 'game_dingque_push');
        beginChecking(s.userId, i, game.state);
    }

};

exports.gmfn = function (userId, fnStr) {
    var { content } = fnStr;
    gmFnDict.uid = userId;
    var arr = content.split(',');
    // 赋全局值
    gmFnDict.type = arr[0];
    gmFnDict.fn = arr[1];
    gmFnDict.nums = arr[2];
    if (arr[3]) {
        gmFnDict.uid = arr[3];
    }
    console.log(gmFnDict, 'gm dict');
}
// 出牌阶段
exports.chuPai = function (userId, pai) {
    pai = Number.parseInt(pai);
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        console.log("can't find user game data.");
        return;
    }

    var game = seatData.game;
    var seatIndex = seatData.seatIndex;
    // 如果不该他出，则忽略
    if (game.turn != seatData.seatIndex) {
        console.log("not your turn.");
        return;
    }

    if (seatData.hued) {
        console.log('you have already hued. no kidding plz.');
        return;
    }
    if (!seatData.canChuPai) {
        console.log('no need chupai.');
        return;
    }

    if (hasOperations(seatData)) {
        console.log('plz guo before you chupai.');
        return;
    }

    console.log('-----出牌----------', seatData.holds, pai);
    // 从此人牌中扣除
    var index = seatData.holds.indexOf(pai);
    if (index == -1) {
        console.log("holds: " + seatData.holds);
        console.log("can't find mj: " + pai);
        return;
    }

    seatData.canChuPai = false;
    game.chupaiCnt++;
    seatData.guoHuFan = -1;

    seatData.holds.splice(index, 1);
    seatData.countMap[pai]--;
    game.chuPai = pai;
    recordGameAction(game, seatData.seatIndex, ACTION_CHUPAI, pai);
    checkCanTingPai(game, seatData);

    userMgr.broacastInRoom('game_chupai_notify_push', { userId: seatData.userId, pai: pai }, seatData.userId, true);

    // 如果出的牌可以胡，则算过胡
    if (seatData.tingMap[game.chuPai]) {
        seatData.guoHuFan = seatData.tingMap[game.chuPai].fan;
    }

    // 检查是否有人要胡，要碰 要杠 吃
    var hasActions = false;
    for (var i = 0; i < game.gameSeats.length; ++i) {
        // 玩家自己不检查
        if (game.turn == i) {
            continue;
        }
        var ddd = game.gameSeats[i];
        // 已经和牌的不再检查
        if (ddd.hued) { continue; }

        checkCanHu(game, ddd, pai);
        if (seatData.lastFangGangSeat == -1) {
            if (ddd.canHu && ddd.guoHuFan >= 0 && ddd.tingMap[pai].fan <= ddd.guoHuFan) {
                console.log("ddd.guoHuFan:" + ddd.guoHuFan);
                ddd.canHu = false;
                userMgr.sendMsg(ddd.userId, 'guohu_push');
            }
        }
        checkCanPeng(game, ddd, pai);
        // 吃牌只对上家
        const isShangJia = seatIndex == 3 ? i == 0 : seatIndex == i - 1;
        // console.log('---------------判断上家----------------', seatIndex, i, isShangJia);
        if (isShangJia) {
            checkCanChi(game, ddd, pai);
        }
        checkCanDianGang(game, ddd, pai);
        // mjutils.matchShun(ddd, pai);
        // 特殊杠只能自己摸
        // checkSpecialGang(game, ddd);

        if (hasOperations(ddd)) {
            sendOperations(game, ddd, game.chuPai);
            hasActions = true;
        }
    }


    // 如果没有人有操作，则向下一家发牌，并通知他出牌
    if (!hasActions) {
        setTimeout(function () {
            userMgr.broacastInRoom('guo_notify_push', { userId: seatData.userId, pai: game.chuPai }, seatData.userId, true);
            seatData.folds.push(game.chuPai);
            game.chuPai = -1;
            moveToNextUser(game);
            doUserMoPai(game);
        }, 500);
    }
};
// 碰
exports.peng = function (userId) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        console.log("can't find user game data.");
        return;
    }

    var game = seatData.game;

    //如果是他出的牌，则忽略
    if (game.turn == seatData.seatIndex) {
        console.log("it's your turn.");
        return;
    }

    //如果没有碰的机会，则不能再碰
    if (!seatData.canPeng) {
        console.log("seatData.peng == false");
        return;
    }
    // 和的了，就不要再来了
    if (seatData.hued) {
        console.log('you have already hued. no kidding plz.');
        return;
    }

    //如果有人可以胡牌，则需要等待
    var i = game.turn;
    while (true) {
        var i = (i + 1) % 4; // game.numOfSeats;
        if (i == game.turn) {
            break;
        } else {
            var gs = game.gameSeats[i];
            if (gs.canHu && i != seatData.seatIndex) {
                return;
            }
        }
    }

    clearAllOptions(game);
    // clearContinuousGangs(game);

    //验证手上的牌的数目
    var pai = game.chuPai;
    var c = seatData.countMap[pai];
    if (c == null || c < 2) {
        console.log("pai:" + pai + ",count:" + c);
        // console.log(seatData.holds);
        console.log("lack of mj.");
        return;
    }

    //进行碰牌处理
    //扣掉手上的牌
    //从此人牌中扣除
    for (var i = 0; i < 2; ++i) {
        var index = seatData.holds.indexOf(pai);
        if (index == -1) {
            console.log("can't find mj.");
            return;
        }

        seatData.holds.splice(index, 1);
        seatData.countMap[pai]--;
    }

    seatData.pengs.push(pai);
    game.chuPai = -1;

    recordGameAction(game, seatData.seatIndex, ACTION_PENG, pai);

    //广播通知其它玩家
    userMgr.broacastInRoom('peng_notify_push', { userid: seatData.userId, pai: pai }, seatData.userId, true);

    //碰的玩家打牌
    moveToNextUser(game, seatData.seatIndex);

    // checkCanAnGang(game, seatData);    
    // checkCanWanGang(game, seatData);    
    // checkCanMingPai(game, seatData);

    //广播通知玩家出牌方
    seatData.canChuPai = true;
    userMgr.broacastInRoom('game_chupai_push', seatData.userId, seatData.userId, true);

    // //通知玩家做对应操作
    // sendOperations(game, seatData, game.chuPai);

};
exports.chi = function (userId, paistr) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        console.log("can't find user game data.");
        return;
    }

    var game = seatData.game;

    // 如果是他出的牌，则忽略
    if (game.turn == seatData.seatIndex) {
        console.log("it's your turn.");
        return;
    }

    // 如果没有吃的机会
    if (!seatData.canChi) {
        console.log("seatData.peng == false");
        return;
    }
    // 和的了，就不要再来了
    if (seatData.hued) {
        console.log('you have already hued. no kidding plz.');
        return;
    }

    // 如果有人可以胡牌，则需要等待
    var i = game.turn;
    while (true) {
        var i = (i + 1) % 4; // game.numOfSeats;
        if (i == game.turn) {
            break;
        } else {
            var gs = game.gameSeats[i];
            if (gs.canHu && i != seatData.seatIndex) {
                return;
            }
        }
    }

    clearAllOptions(game);
    // 验证手上的牌的数目
    var pai = game.chuPai;
    // var c = seatData.countMap[pai];
    // if (c == null || c < 2) {
    //     console.log("pai:" + pai + ",count:" + c);
    //     // console.log(seatData.holds);
    //     console.log("lack of mj.");
    //     return;
    // }

    // 进行吃牌处理
    // 扣掉手上的牌
    // 从此人牌中扣除
    const pai_arr = paistr.split(',').map(e => +e);
    console.log('pai str', pai, paistr, pai_arr);
    console.log(seatData.holds, '------此时手牌');
    for (let i = 0; i < pai_arr.length; i++) {
        const el = pai_arr[i];
        if (el == pai) {
            continue;
        }
        var index = seatData.holds.indexOf(el);
        if (index == -1) {
            console.log("can't find mj.");
            return;
        }
        seatData.holds.splice(index, 1);
        seatData.countMap[el]--;
    }
    console.log('扣除后手牌--------', seatData.holds);
    // pai_arr.forEach(e_pai => {
    //     var index = seatData.holds.indexOf(e_pai);
    //     if (e_pai == pai) {
    //     }
    //     if (index == -1) {
    //         console.log("can't find mj.");
    //         return;
    //     }
    //     seatData.holds.splice(index, 1);
    //     seatData.countMap[e_pai]--;
    // });

    // for (var i = 0; i < 2; ++i) {
    //     var index = seatData.holds.indexOf(pai);
    //     if (index == -1) {
    //         console.log("can't find mj.");
    //         return;
    //     }
    //     seatData.holds.splice(index, 1);
    //     seatData.countMap[pai]--;
    // }

    seatData.chis.push(paistr);
    game.chuPai = -1;

    recordGameAction(game, seatData.seatIndex, ACTION_CHI, pai);

    //广播通知其它玩家
    userMgr.broacastInRoom('chi_notify_push', { userid: seatData.userId, pai: pai, chipai: paistr }, seatData.userId, true);

    //chi的玩家打牌
    moveToNextUser(game, seatData.seatIndex);

    //广播通知玩家出牌方
    seatData.canChuPai = true;
    userMgr.broacastInRoom('game_chupai_push', seatData.userId, seatData.userId, true);

};
exports.isPlaying = function (userId) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        return false;
    }

    var game = seatData.game;

    if (game.state == "idle") {
        return false;
    }

    return true;
}

// 杠
exports.gang = function (userId, pai) {
    console.log('客户端上传的pai', pai);
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        console.log("can't find user game data.");
        return;
    }

    var seatIndex = seatData.seatIndex;
    var game = seatData.game;

    //如果没有杠的机会，则不能再杠
    if (!seatData.canGang) {
        console.log("seatData.gang == false");
        return;
    }
    // 和的了，就不要再来了
    if (seatData.hued) {
        console.log('you have already hued. no kidding plz.');
        return;
    }

    // pai 非数字杠
    if (isNaN(pai)) {
        // spc_sign = true;
        // doSpecialGang(game, turnSeat, seatData, gangtype, numOfCnt, pai);
        // turnSeat不会清空，不会出现多个重复杠？
        // var turnSeat = game.gameSeats[game.turn];

        // 深拷贝
        // const gangPai = JSON.parse(JSON.stringify(seatData.gangPai));
        // 出牌状态初始
        game.chuPai = -1;
        // 去掉遮罩
        clearAllOptions(game);
        // 不用出牌
        seatData.canChuPai = false;

        userMgr.broacastInRoom('hangang_notify_push', seatIndex, seatData.userId, true);

        // 处理特殊杠
        doSpecialGang(game, seatData, pai);
        return;
    }
    if (seatData.gangPai.indexOf(pai) == -1) {
        // 普通杠牌
        console.log("the given pai can't be ganged.");
        return;
    }

    //如果有人可以胡牌，则需要等待
    var i = game.turn;
    while (true) {
        var i = (i + 1) % 4; // game.numOfSeats;
        if (i == game.turn) {
            break;
        } else {
            var gs = game.gameSeats[i];
            if (gs.canHu && i != seatData.seatIndex) {
                return;
            }
        }
    }
    var numOfCnt = seatData.countMap[pai];
    var gangtype = "";
    //弯杠 去掉碰牌
    if (numOfCnt == 1) {
        gangtype = "wangang"
    } else if (numOfCnt == 3) {
        gangtype = "diangang"
    } else if (numOfCnt == 4) {
        gangtype = "angang";
    } else {
        console.log("invalid pai count.");
        return;
    }

    game.chuPai = -1;
    clearAllOptions(game);
    seatData.canChuPai = false;

    userMgr.broacastInRoom('hangang_notify_push', seatIndex, seatData.userId, true);

    //如果是弯杠，则需要检查是否可以抢杠
    var turnSeat = game.gameSeats[game.turn];
    if (numOfCnt == 1) {
        var canQiangGang = checkCanQiangGang(game, turnSeat, seatData, pai);
        if (canQiangGang) {
            return;
        }
    }

    doGang(game, turnSeat, seatData, gangtype, numOfCnt, pai);
};
// 胡
exports.hu = function (userId) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        console.log("can't find user game data.");
        return;
    }

    var seatIndex = seatData.seatIndex;
    var game = seatData.game;

    //如果他不能和牌，那和个啥啊
    if (seatData.canHu == false) {
        console.log("invalid request.");
        return;
    }

    //和的了，就不要再来了
    if (seatData.hued) {
        console.log('you have already hued. no kidding plz.');
        return;
    }
    try {
        //标记为和牌
        seatData.hued = true;
        var hupai = game.chuPai;
        var isZimo = false;

        var turnSeat = game.gameSeats[game.turn];
        seatData.isGangHu = turnSeat.lastFangGangSeat >= 0;
        var notify = -1;

        if (game.qiangGangContext != null) {
            var gangSeat = game.qiangGangContext.seatData;
            hupai = game.qiangGangContext.pai;
            notify = hupai;

            var ac = recordUserAction(game, seatData, "qiangganghu", gangSeat.seatIndex);
            ac.iszimo = false;
            recordGameAction(game, seatIndex, ACTION_HU, hupai);
            seatData.isQiangGangHu = true;
            game.qiangGangContext.isValid = false;


            var idx = gangSeat.holds.indexOf(hupai);
            if (idx != -1) {
                gangSeat.holds.splice(idx, 1);
                gangSeat.countMap[hupai]--;
                userMgr.sendMsg(gangSeat.userId, 'game_holds_push', gangSeat.holds);
            }
            //将牌添加到玩家的手牌列表，供前端显示
            seatData.holds.push(hupai);
            if (seatData.countMap[hupai]) {
                seatData.countMap[hupai]++;
            }
            else {
                seatData.countMap[hupai] = 1;
            }

            recordUserAction(game, gangSeat, "beiqianggang", seatIndex);
        } else if (game.chuPai == -1) {
            hupai = seatData.holds[seatData.holds.length - 1];
            notify = -1;
            if (seatData.isGangHu) {
                if (turnSeat.lastFangGangSeat == seatIndex) {
                    var ac = recordUserAction(game, seatData, "ganghua");
                    ac.iszimo = true;
                } else {
                    var diangganghua_zimo = game.conf.dianganghua == 1;
                    if (diangganghua_zimo) {
                        var ac = recordUserAction(game, seatData, "dianganghua");
                        ac.iszimo = true;
                    } else {
                        var ac = recordUserAction(game, seatData, "dianganghua", turnSeat.lastFangGangSeat);
                        ac.iszimo = false;
                    }
                }
            } else {
                var ac = recordUserAction(game, seatData, "zimo");
                ac.iszimo = true;
            }

            isZimo = true;
            recordGameAction(game, seatIndex, ACTION_ZIMO, hupai);
        } else {
            notify = game.chuPai;
            //将牌添加到玩家的手牌列表，供前端显示
            seatData.holds.push(game.chuPai);
            if (seatData.countMap[game.chuPai]) {
                seatData.countMap[game.chuPai]++;
            } else {
                seatData.countMap[game.chuPai] = 1;
            }

            console.log(seatData.holds, 'hu holds');

            var at = "hu";
            //炮胡
            if (turnSeat.lastFangGangSeat >= 0) {
                at = "gangpaohu";
            }

            var ac = recordUserAction(game, seatData, at, game.turn);
            ac.iszimo = false;

            //毛转雨
            if (turnSeat.lastFangGangSeat >= 0) {
                for (var i = turnSeat.actions.length - 1; i >= 0; --i) {
                    var t = turnSeat.actions[i];
                    if (t.type == "diangang" || t.type == "wangang" || t.type == "angang" || t.type == 'teshugang') {
                        t.state = "nop";
                        t.payTimes = 0;

                        var nac = {
                            type: "maozhuanyu",
                            owner: turnSeat,
                            ref: t
                        }
                        seatData.actions.push(nac);
                        break;
                    }
                }
            }

            //记录玩家放炮信息
            var fs = game.gameSeats[game.turn];
            recordUserAction(game, fs, "fangpao", seatIndex);

            recordGameAction(game, seatIndex, ACTION_HU, hupai);

            game.fangpaoshumu++;

            if (game.fangpaoshumu > 1) {
                game.yipaoduoxiang = seatIndex;
            }
        }

        if (game.firstHupai < 0) {
            game.firstHupai = seatIndex;
        }

        // 保存番数
        var ti;
        if (seatData.useHuiPai) {
            ti = seatData.tingMap[game.realhuipai];
            seatData.fan = ti.fan;
            seatData.pattern = ti.pattern;
        } else {
            ti = seatData.tingMap[hupai];
            seatData.fan = ti.fan;
            seatData.pattern = ti.pattern;
        }
        seatData.iszimo = isZimo;
        //如果是最后一张牌，则认为是海底胡
        seatData.isHaiDiHu = game.currentIndex == game.mahjongs.length;
        game.hupaiList.push(seatData.seatIndex);
        console.log(seatData.fan, 'fan shu');

        if (game.conf.tiandihu) {
            if (game.chupaiCnt == 0 &&
                game.button == seatData.seatIndex &&
                game.chuPai == -1) {
                seatData.isTianHu = true;
            } else if (game.chupaiCnt == 1 &&
                game.turn == game.button &&
                game.button != seatData.seatIndex &&
                game.chuPai != -1) {
                seatData.isDiHu = true;
            }
        }

        clearAllOptions(game, seatData);

        //通知前端，有人和牌了
        userMgr.broacastInRoom('hu_push', {
            seatindex: seatIndex,
            iszimo: isZimo,
            hupai: notify
        }, seatData.userId, true);

        //
        if (game.lastHuPaiSeat == -1) {
            game.lastHuPaiSeat = seatIndex;
        } else {
            var lp = (game.lastFangGangSeat - game.turn + 4) % 4;
            var cur = (seatData.seatIndex - game.turn + 4) % 4;
            if (cur > lp) {
                game.lastHuPaiSeat = seatData.seatIndex;
            }
        }

        //如果只有一家没有胡，则结束
        var numOfHued = 0;
        for (var i = 0; i < game.gameSeats.length; ++i) {
            var ddd = game.gameSeats[i];
            if (ddd.hued) {
                numOfHued++;
            }
        }

        // 清空所有非胡牌操作
        for (var i = 0; i < game.gameSeats.length; ++i) {
            var ddd = game.gameSeats[i];
            ddd.canPeng = false;
            ddd.canChi = false;
            ddd.canGang = false;
            ddd.canChuPai = false;
            sendOperations(game, ddd, hupai);
        }
        // 和了就结束
        if (numOfHued == 1) {
            doGameOver(game, seatData.userId);
            return;
        }

        // //如果还有人可以胡牌，则等待
        // for (var i = 0; i < game.gameSeats.length; ++i) {
        //     var ddd = game.gameSeats[i];
        //     if (ddd.canHu) {
        //         return;
        //     }
        // }

        // //和牌的下家继续打
        // clearAllOptions(game);
        // game.turn = game.lastHuPaiSeat;
        // moveToNextUser(game);
        // doUserMoPai(game);

    } catch (error) {
        console.log('hu error', error);
    }
};
// 过
exports.guo = function (userId) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        console.log("can't find user game data.");
        return;
    }

    var seatIndex = seatData.seatIndex;
    var game = seatData.game;

    //如果玩家没有对应的操作，则也认为是非法消息
    if (!hasOperations(seatData)) {
        console.log("no need guo.");
        return;
    }

    //如果是玩家自己的轮子，不是接牌，则不需要额外操作
    var doNothing = game.chuPai == -1 && game.turn == seatIndex;

    userMgr.sendMsg(seatData.userId, "guo_result");
    clearAllOptions(game, seatData);

    //这里还要处理过胡的情况
    if (game.chuPai >= 0 && seatData.canHu) {
        seatData.guoHuFan = seatData.tingMap[game.chuPai].fan;
    }

    if (doNothing) { return; }

    // 如果一炮多响，第二个胡牌的人选择了过，应该结束游戏
    if (game.firstHupai >= 0) {
        doGameOver(game, userId);
        return;
    }
    // 如果还有人可以操作，则等待
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var gs = game.gameSeats[i];
        if (hasOperations(gs)) {
            // if (!gs.canHu) {
            //     sendOperations(game, gs, game.chuPai);
            // }
            return;
        }
    }

    //如果是已打出的牌，则需要通知
    if (game.chuPai >= 0) {
        var uid = game.gameSeats[game.turn].userId;
        userMgr.broacastInRoom('guo_notify_push', { userId: uid, pai: game.chuPai }, seatData.userId, true);

        // 不一致
        // var gs = game.gameSeats[game.turn];
        // gs.folds.push(game.chuPai);
        seatData.folds.push(game.chuPai);
        game.chuPai = -1;
    }

    var qiangGangContext = game.qiangGangContext;
    //清除所有的操作
    clearAllOptions(game);

    if (qiangGangContext != null && qiangGangContext.isValid) {
        const { turnSeat, seatData, pai } = qiangGangContext;
        doGang(game, turnSeat, seatData, "wangang", 1, pai);
    } else {
        //下家摸牌
        moveToNextUser(game);
        doUserMoPai(game);
    }
};

exports.hasBegan = function (roomId) {
    var game = games[roomId];
    if (game != null) {
        return true;
    }

    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo != null) {
        return roomInfo.numOfGames > 0;
    }

    return false;
};

exports.doDissolve = function (roomId) {
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return null;
    }

    var game = games[roomId];
    doGameOver(game, roomInfo.seats[0].userId, true);
};

exports.dissolveUpdate = function (roomId, userId, online) {
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return null;
    }

    // var seatIndex = roomMgr.getUserSeatId(userId);
    var seatIndex = roomMgr.getUserSeat(userId);
    if (seatIndex == null) {
        return null;
    }

    var dr = roomInfo.dr;

    if (dr == null) {
        if (!online) {
            return exports.dissolveRequest(roomId, userId, true);
        } else {
            return null;
        }
    }

    dr.online[seatIndex] = online;

    var found = false;
    var reject = -1;
    for (var i = 0; i < dr.online.length; i++) {
        if (!dr.online[i]) {
            found = true;
        }

        if (dr.states[i] == 1) {
            reject = roomInfo.seats[i].userId;
        }
    }

    if (!found) {
        if (dr.reason == 'offline' || reject >= 0) {
            if (reject >= 0) {
                roomInfo.rejectUser = reject;
            }

            roomInfo.dr = null;
            var idx = dissolvingList.indexOf(roomId);
            if (idx != -1) {
                dissolvingList.splice(idx, 1);
            }
        }
    }

    return roomInfo;
};

exports.dissolveRequest = function (roomId, userId) {
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return null;
    }

    if (roomInfo.dr != null) {
        return null;
    }

    var seatIndex = roomMgr.getUserSeat(userId);
    if (seatIndex == null) {
        return null;
    }

    roomInfo.dr = {
        endTime: Date.now() + 30000,
        states: [false, false, false, false]
    };
    roomInfo.dr.states[seatIndex] = true;

    dissolvingList.push(roomId);

    if (gmFnDict.fn && gmFnDict.type) {
        // 重置
        // gmFnDict = {
        //     uid: '', // 指定修改userid的牌
        //     type: '',
        //     fn: '', // 1为开局指定配牌，2为游戏中下一张牌配牌，3为
        //     nums: '', // NO.对应指令
        // }
        Object.keys(gmFnDict).forEach(e => {
            gmFnDict[e] = '';
        });
    }
    console.log('reset room ready', gmFnDict);

    return roomInfo;
};

exports.dissolveAgree = function (roomId, userId, agree) {
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return null;
    }

    var dr = roomInfo.dr;
    if (dr == null) {
        return null;
    }

    // var seatIndex = roomMgr.getUserSeatId(userId);
    var seatIndex = roomMgr.getUserSeat(userId);
    if (seatIndex == null) {
        return null;
    }

    // if (agree) {
    //     dr.states[seatIndex] = 2;
    //     var count = 0;
    //     for (var i = 0; i < dr.states.length; i++) {
    //         if (dr.states[i] >= 2) {
    //             count++;
    //         }
    //     }

    //     if (2 == count) {
    //         dr.endTime = Date.now() + 300000;
    //     }
    // } else {
    //     dr.states[seatIndex] = 1;

    //     var found = false;
    //     for (var i = 0; i < dr.online.length; i++) {
    //         if (!dr.online[i]) {
    //             found = true;
    //             break;
    //         }
    //     }

    //     if (!found) {
    //         roomInfo.dr = null;
    //         var idx = dissolvingList.indexOf(roomId);
    //         if (idx != -1) {
    //                 dissolvingList.splice(idx, 1);           
    //         }
    //     }
    // }
    if (agree) {
        roomInfo.dr.states[seatIndex] = true;
    } else {
        roomInfo.dr = null;
        var idx = dissolvingList.indexOf(roomId);
        if (idx != -1) {
            dissolvingList.splice(idx, 1);
        }
    }

    return roomInfo;
};


exports.parseConf = function (roomConf, conf) {
    conf.dingpiao = roomConf.dingpiao ? true : false;
    conf.maima = roomConf.maima || 0;

    var type = roomConf.type;
    if (type == 'xykwx') {
        conf.pindao = roomConf.pindao || 0;
    } else if (type == 'xgkwx') {
        conf.shukan = roomConf.shukan;
        conf.chkming = roomConf.chkming;
    } else if (type == 'szkwx') {
        conf.partming = roomConf.partming;
    } else if (type == 'sykwx') {
        conf.pindao = roomConf.pindao || 0;
        conf.up = roomConf.up;
        conf.chajiao = roomConf.chajiao;
    } else if (type == 'yckwx') {
        conf.pqmb = roomConf.pqmb;
    }
}

exports.checkConf = function () {
    return true;
}

setInterval(update, 1000);
